package dht.table;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Vector;

import dht.IClientRouteTable;
import dht.IEntity;
import dht.entity.GlobalVariables;
import dht.entity.NodeInfo;
import dht.storage.PathUtils;

public class ClientRouteTableImpl implements IClientRouteTable, IEntity {

	private static final long serialVersionUID = 1L;
	protected int ringLength;
	protected NodeInfo masterInfo;
	protected HashMap<String, NodeInfo> registedServer;
	protected HashMap<Integer, HashSet<String>> vnodeId2ServerIds;

	public ClientRouteTableImpl(NodeInfo masterInfo) {
		this.ringLength = GlobalVariables.ringLength;
		this.masterInfo = masterInfo;
		this.registedServer = new HashMap<String, NodeInfo>();
		this.vnodeId2ServerIds = new HashMap<Integer, HashSet<String>>();
		for (int i = 0; i < ringLength; ++i) {
			vnodeId2ServerIds.put(i, new HashSet<String>());
		}
	}

	@Override
	public synchronized Object clone() {
		Object o = null;
		try {
			o = super.clone();
		} catch (CloneNotSupportedException e) {
			throw new InternalError();
		}
		return o;
	}

	public synchronized Object tableClone() {
		try {
			ClientRouteTableImpl m = (ClientRouteTableImpl) super.clone();
			if (masterInfo != null) {
				m.masterInfo = (NodeInfo) masterInfo.clone();
			}
			if (registedServer != null) {
				m.registedServer = new HashMap<String, NodeInfo>();
				for (String serverId : registedServer.keySet()) {
					m.registedServer
							.put(serverId, registedServer.get(serverId));
				}
			}
			if (vnodeId2ServerIds != null) {
				m.vnodeId2ServerIds = new HashMap<Integer, HashSet<String>>();
				for (Integer vnodeId : vnodeId2ServerIds.keySet()) {
					HashSet<String> serverIds = vnodeId2ServerIds.get(vnodeId);
					if (serverIds != null) {
						HashSet<String> newServerIds = new HashSet<String>();
						for (String serverId : serverIds) {
							newServerIds.add(serverId);
						}
						m.vnodeId2ServerIds.put(vnodeId, newServerIds);
					} else {
						m.vnodeId2ServerIds.put(vnodeId, null);
					}
				}
			}
			return m;
		} catch (CloneNotSupportedException e) {
			throw new InternalError();
		}
	}

	@Override
	public Vector<NodeInfo> getServerList(String fileName) {
		int vnodeId = getVnodeId(fileName);
		Vector<NodeInfo> serverInfoList = new Vector<NodeInfo>();
		HashSet<String> serverIdList = vnodeId2ServerIds.get(vnodeId);
		for (String serverId : serverIdList) {
			serverInfoList.add(registedServer.get(serverId));
		}
		return serverInfoList;
	}

	@Override
	public NodeInfo getNearestServer(String fileName, NodeInfo nodeInfo) {
		Vector<NodeInfo> vecServerInfo = getServerList(fileName);
		NodeInfo serverInfo = getNearestServer(nodeInfo, vecServerInfo);
		return serverInfo;
	}

	@Override
	public NodeInfo getNearestServer(NodeInfo nodeInfo) {
		Vector<NodeInfo> vecServerInfo = (Vector<NodeInfo>) registedServer
				.values();
		NodeInfo nearestServerInfo = getNearestServer(nodeInfo, vecServerInfo);
		return nearestServerInfo;
	}

	@Override
	public NodeInfo getMasterInfo() {
		return masterInfo;
	}

	private NodeInfo getNearestServer(NodeInfo nodeInfo,
			Vector<NodeInfo> vecServerInfo) {
		NodeInfo nearestServerInfo = null;
		double nearestDistance = Double.MAX_VALUE;
		for (NodeInfo serverInfo : vecServerInfo) {
			double distance = serverInfo.distanceTo(nodeInfo);
			if (nearestDistance > distance) {
				nearestDistance = distance;
				nearestServerInfo = serverInfo;
			}
		}
		return nearestServerInfo;
	}

	protected int getVnodeId(String fileName) {
		return PathUtils.getVnodeId(fileName);
	}

}
